﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Noris.Tools.FrxEditor.Components;
using Noris.Tools.FrxEditor.Editor;
using Noris.Tools.FrxEditor.ComponentsLayer;

namespace Noris.Tools.FrxEditor.Editor
{
    #region class EditPaintEventArgs : argumenty pro kreslící metody v prostoru ViewPort
    /// <summary>
    /// EditPaintEventArgs : argumenty pro kreslící metody v prostoru ViewPort.
    /// Je potomkem EditEventArgs.
    /// </summary>
    public class EditPaintEventArgs : EditEventArgs, IDisposable
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="viewPortConvertor"></param>
        /// <param name="graphics"></param>
        /// <param name="visualArea"></param>
        /// <param name="mouseState"></param>
        public EditPaintEventArgs(IViewPortConvertor viewPortConvertor, EditorObjectState objectState, Graphics graphics, RectangleF visualArea, EditMouseState mouseState)
            : base(viewPortConvertor, objectState)
        {
            this.Graphics = graphics;
            this.VisualArea = visualArea;
            this.Reset(mouseState);
        }
        /// <summary>
        /// Resetuje objekt před jeho předáním do metody Draw().
        /// Objekt se používá jeden pro kreslení všech prvků v jednom cyklu.
        /// </summary>
        internal void Reset(EditMouseState mouseState)
        {
            this.MouseState = mouseState;
            this.Reset();
        }
        /// <summary>
        /// Resetuje objekt před jeho předáním do metody Draw().
        /// Objekt se používá jeden pro kreslení všech prvků v jednom cyklu.
        /// </summary>
        internal void Reset()
        {
            this.IsVisible = false;
            this.IsInteractive = false;
            this.ActiveVisualAreaList = new List<SheetF>();
        }
        /// <summary>
        /// PaintEventArgs.Graphics: Gets the graphics used to paint.
        /// </summary>
        /// <value>The System.Drawing.Graphics object used to paint. The System.Drawing.Graphics object provides methods for drawing objects on the display device.</value>
        public Graphics Graphics { get; private set; }
		/// <summary>
		/// Souřadnice aktuálně vykreslované plochy, vizuální (v reálných pixelech).
		/// </summary>
        public RectangleF VisualArea { get; private set; }
        /// <summary>
        /// Aktuální stav myši
        /// </summary>
        public EditMouseState MouseState { get; private set; }
        /// <summary>
        /// Zde element nastavuje příznak, zda byl fyzicky vykreslen (true) nebo ne (false).
        /// To záleží na jeho stavu a na jeho pozici, přepočtené přes měřítko, zda se nachází ve viditelném rámci.
        /// Výchozí hodnota je false = nevykresleno.
        /// Nevykreslené objekty se nebudou zahrnovat do interaktivních dějů v editoru.
        /// </summary>
        public bool IsVisible { get; set; }
        /// <summary>
        /// Zde element nastavuje příznak, zda je interaktivní = zda se s ním má počítat při pohybech myši (true) nebo ne (false).
        /// To záleží na jeho typu: neinteraktivní prvek je pouze pozadí Band v ploše Components (tj. pozadí v editační ploše).
        /// Vše ostatní je jistým způsobem interaktivní, a to i Fixed komponenty (nepohyblivé) = mohou mít svoje kontextové menu a okno s detailem.
        /// Výchozí hodnota je false = neinteraktivní.
        /// Neinteraktivní objekty se nebudou zahrnovat do interaktivních dějů v editoru.
        /// </summary>
        public bool IsInteractive { get; set; }
        /// <summary>
        /// Seznam souřadnic (SheetF), na kterých se nachází tento element.
        /// Element může zabírat více než jeden SheetF, v jehož rámci má být aktivní.
        /// Typicky v klidu zabírá jeden SheetF, ale pokud má vykresleny hranové a vrcholové úchytky, pak se o ně zvětšuje jeho aktivní prostor.
        /// Pokud je v IsVisible = false, pak je zde prázdný List.
        /// </summary>
        public List<SheetF> ActiveVisualAreaList { get; set; }
		/// <summary>
		/// Vrátí true, pokud dané souřadnice budou alespoň zčásti viditelné
		/// </summary>
		/// <param name="visualBound"></param>
		/// <returns></returns>
		public bool DetectVisible(RectangleF visualBound)
		{
			return (visualBound.IntersectsWith(this.VisualArea));
		}
        void IDisposable.Dispose()
		{
            this.Graphics = null;
            this.ActiveVisualAreaList = null;
		}
    }
    #endregion
    #region class EditInteractiveEventArgs : argumenty pro interaktivní metody v prostoru ViewPort
    /// <summary>
    /// EditEventArgs : argumenty pro interaktivní metody v prostoru ViewPort
    /// Je potomkem EditEventArgs.
    /// </summary>
    public class EditInteractiveEventArgs : EditEventArgs, IDisposable
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="viewPortConvertor"></param>
        /// <param name="mousePointF"></param>
        /// <param name="mousePointFDown"></param>
        /// <param name="mouseChange"></param>
        public EditInteractiveEventArgs(IViewPortConvertor viewPortConvertor, EditorObjectState objectState, PointF mousePointF, PointF mousePointFDown, EditMouseChangeType mouseChange)
            : base(viewPortConvertor, objectState)
        {
            this.MousePointF = mousePointF;
            this.MousePointFDown = mousePointFDown;
            this.MouseChange = mouseChange;
            this._ReDrawLayerDict = new Dictionary<int, object>();
			this.NeedCursor = SysCursorType.Null;
        }
        /// <summary>
        /// Bod, na kterém se nachází myš.
        /// Koordináty = pixely controlu
        /// </summary>
        public PointF MousePointF { get; private set; }
        /// <summary>
        /// Bod, na kterém se nacházela myš, když se stiskla.
        /// Je vyplněno pouze v událostech, kde je myš stisknutá anebo se zvedá (MouseUp).
        /// Koordináty = pixely controlu
        /// </summary>
        public PointF MousePointFDown { get; private set; }
        /// <summary>
        /// Obsahuje ve formě PointF posun aktuální pozice myši (MousePointF) oproti bodu, kde byla stisknuta (MousePointFDown).
        /// Při posunu doleva obsahuje zápornou souřadnici X, atd.
        /// </summary>
        public PointF MouseMoveOffset
        {
            get { return new PointF(this.MousePointF.X - this.MousePointFDown.X, this.MousePointF.Y - this.MousePointFDown.Y); }
        }
        /// <summary>
        /// Metoda vrátí daný bod posunutý shodně, jako se posunula myš (z bodu MousePointFDown do bodu MousePointF).
        /// </summary>
        /// <param name="originalPoint"></param>
        /// <returns></returns>
        public PointF GetMovedPoint(PointF originalPoint)
        {
            PointF offset = this.MouseMoveOffset;
            return new PointF(originalPoint.X + offset.X, originalPoint.Y + offset.Y);
        }
        /// <summary>
        /// Stav myši nebo změna stavu
        /// </summary>
        public EditMouseChangeType MouseChange { get; internal set; }
        /// <summary>
        /// Aktivní prvek
        /// </summary>
        public EditorItem CurrentActiveItem { get; internal set; }
        /// <summary>
        /// Segment aktivního prvku
        /// </summary>
        public EditorItemSegment CurrentActiveSegment { get; internal set; }
        /// <summary>
        /// Vrátí true, pokud this.MouseChange má některou z předaných hodnot (if this.MouseChange in (list...))
        /// </summary>
        /// <param name="states"></param>
        /// <returns></returns>
        public bool IsMouseChange(params EditMouseChangeType[] states)
        {
            foreach (EditMouseChangeType state in states)
            {
                if (this.MouseChange == state) return true;
            }
            return false;
        }
        /// <summary>
        /// Out: akce myši vyžaduje ReDraw některých vrstev controlu. Pokud je true, pak LayerToDraw obsahuje kladný počet vrstev k překreslení.
        /// </summary>
        public bool NeedDraw { get { return (this._ReDrawLayerDict.Count > 0); } }
        /// <summary>
        /// Zajistí vykreslení daných vrstev controlu.
        /// Vrstva 0 = podklad (BandHeader, BandRow, Splitters, Rulers, Scrollbars).
        /// Vrstva 1 = Components v klidovém stavu nebo pod myší, když není stisknuta (neprobíhá přesouvání).
        /// Vrstva 2 = Components ve stavu Drag (přesouvá se nebo resizuje).
        /// Opakované přidání některé vrstvy není problémem, vykreslí se sumárně požadované vrstvy jedním ReDraw().
        /// Ignoruje přidávání vrstev jiných než jsou vrstvy zde uvedené (0,1,2).
        /// </summary>
        /// <param name="layer"></param>
        public void ReDrawLayers(params int[] layers)
        {
            foreach (int layer in layers)
                this.ReDrawLayer(layer);
        }
        /// <summary>
        /// Zajistí vykreslení dané vrstvy controlu.
        /// Vrstva 0 = podklad (BandHeader, BandRow, Splitters, Rulers, Scrollbars).
        /// Vrstva 1 = Components v klidovém stavu nebo pod myší, když není stisknuta (neprobíhá přesouvání).
        /// Vrstva 2 = Components ve stavu Drag (přesouvá se nebo resizuje).
        /// Opakované přidání některé vrstvy není problémem, vykreslí se sumárně požadované vrstvy jedním ReDraw().
        /// Ignoruje přidávání vrstev jiných než jsou vrstvy zde uvedené (0,1,2).
        /// </summary>
        /// <param name="layer"></param>
        public void ReDrawLayer(int layer)
        {
            if (!this._ReDrawLayerDict.ContainsKey(layer))
                this._ReDrawLayerDict.Add(layer, null);
        }
        /// <summary>
        /// Soupis vrstev k překreslení.
        /// Pomocí property NeedDraw lze testovat, zda je překreslení nutné.
        /// </summary>
        public IEnumerable<int> LayerToDraw { get { return this._ReDrawLayerDict.Keys; } }
        /// <summary>
        /// Out: aktuálně platný kurzor. 
        /// Pamatuje si posledně vloženou hodnotu, neimplementuje žádnou přidanou logiku.
        /// Nadřízená vrstva detekuje změnu požadovaného stavu, a fyzický kurzor mění až při skutečné změně 
        /// (proto je kurzor řešen jako enum SysCursorType, a ne přímo jako Cursor).
        /// </summary>
        public SysCursorType NeedCursor { get; set; }
        private Dictionary<int, object> _ReDrawLayerDict;
        void IDisposable.Dispose()
        {
            this._ReDrawLayerDict = null;
        }
    }
    #endregion
    #region class EditEventArgs : bázová třída pro argumenty editorů, zobrazovačů a prohlížečů
    /// <summary>
    /// EditEventArgs : bázová třída pro argumenty editorů, zobrazovačů a prohlížečů
    /// </summary>
    public class EditEventArgs : EventArgs, IDisposable
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="viewPortConvertor"></param>
        /// <param name="objectState"></param>
        public EditEventArgs(IViewPortConvertor viewPortConvertor, EditorObjectState objectState)
        {
            this.ViewPortConvertor = viewPortConvertor;
            this.ObjectState = objectState;
        }
        /// <summary>
        /// Nástroj zajišťující přepočty souřadnic logických na fyzické a zpátky
        /// </summary>
        public IViewPortConvertor ViewPortConvertor { get; private set; }
        /// <summary>
        /// Stav editoru z hlediska práce s objekty
        /// </summary>
        public EditorObjectState ObjectState { get; private set; }
        void IDisposable.Dispose()
        {
            this.ViewPortConvertor = null;
        }
    }
    #endregion
    #region enum EditMouseChangeType, EditItemInteractivityMode
    /// <summary>
    /// Dynamický stav myši, zohledňuje i přechody mezi stavy MouseState
    /// </summary>
    [Flags]
    public enum EditMouseChangeType
    {
        /// <summary>Myš tu není</summary>
        None = 0,
        /// <summary>Myš přišla</summary>
        Enter = 1,
        /// <summary>Myš se pohybuje nad prvkem (bez stisknutí jakéhokoli tlačítka)</summary>
        Move = Enter << 1,
        /// <summary>Myš se právě stiskla, levý button.
        /// V tuto chvíli ještě není jisté, zda se na myš jenom klikne, nebo zda se s ní bude něco přetahovat (Drag).
        /// Pokud se se stisknutou myší začne hýbat tak, že více než málo (začne Drag), 
        /// vyvolá se událost LeftDragBegin, a potom hned LeftDragMove. 
        /// Po ukončení přetahování se vyvolá událost LeftDragEnd, ale ne LeftUp.
        /// </summary>
        LeftDown = Move << 1,
        /// <summary>Myš je stisknutá (levý button) a právě se začala hýbat (vyběhla z ohrádky).
        /// Bod, na kterém se myš stiskla, je uložen v args.MousePointFDown, 
        /// Bod na kterém se myš nyní nachází je uložen v args.MousePointF.</summary>
        LeftDragBegin = LeftDown << 1,
        /// <summary>Myš je stisknutá (levý button) a hýbe se (začátek byl hlášen stavem LeftDragBegin).
        /// Konec přetahování (po zvednutí myši) bude oznámen událostí LeftDragEnd, ale nebude volána událost LeftUp.</summary>
        LeftDragMove = LeftDragBegin << 1,
        /// <summary>Myš se hýbala v režimu LeftDragMove, a nyní se zvedla. Konec přetahování.</summary>
        LeftDragEnd = LeftDragMove << 1,
        /// <summary>Myš se právě uvolnila, levý button.
        /// Tato událost (LeftUp) se nevolá po ukončení tažení myší (tam se volá pouze LeftDragEnd).
        /// Po události LeftUp se ještě volá událost LeftClick.</summary>
        LeftUp = LeftDragEnd << 1,
        /// <summary>Myš se právě stiskla, pravý button</summary>
        RightDown = LeftUp << 1,
        /// <summary>Myš se právě uvolnila, pravý button</summary>
        RightUp = RightDown << 1,
        /// <summary>Otočení kolečka myši nahoru (Top dokumentu se má zmenšit, ukáže se to co je nahoře)</summary>
        WheelUp = RightUp << 1,
        /// <summary>Otočení kolečka myši dolů (Top dokumentu se má zvětšit, ukáže se to co je dole)</summary>
        WheelDown = WheelUp << 1,
        /// <summary>Myš není stisknutá, a opouští nás</summary>
        Leave = WheelDown << 1,
        /// <summary>Příchod a odchod myši (Enter | Leave)</summary>
        EnterLeave = Enter | Leave,
        /// <summary>Levý click (LeftDown | LeftUp)</summary>
        LeftClick = LeftDown | LeftUp,
        /// <summary>Pravý click (RightDown | RightUp)</summary>
        RightClick = RightDown | RightUp,
        /// <summary>Levý i pravý click (LeftClick | RightClick = (LeftDown | LeftUp) | (RightDown | RightUp))</summary>
        Click = LeftClick | RightClick,
        /// <summary>Levý Drag (LeftDragBegin | LeftDragMove | LeftDragEnd)</summary>
        LeftDrag = LeftDragBegin | LeftDragMove | LeftDragEnd,
        /// <summary>Všechny stavy změny, kromě Move</summary>
        OnChange = Enter | Leave | LeftDown | LeftDragBegin | LeftDragMove | LeftDragEnd | LeftUp | RightDown | RightUp | WheelUp | WheelDown,
        /// <summary>Všechny známé stavy, včetně Move !</summary>
        All = Enter | Move | Leave | LeftDown | LeftDragBegin | LeftDragMove | LeftDragEnd | LeftUp | RightDown | RightUp | WheelUp | WheelDown
    }
    #endregion
    #region interface IInteractiveObject : deklaruje vlastnosti, které musí splňovat objekt, který chce být součástí interaktivní plochy
    /// <summary>
    /// IInteractiveObject : deklaruje vlastnosti, které musí splňovat objekt, který chce být součástí interaktivní plochy
    /// </summary>
    public interface IInteractiveObject
    {
        /// <summary>
        /// Informace o změně stavu a pozice myši.
        /// Objekt může reagovat podle své potřeby, a může žádat o překreslení určité vrstvy controlu + cursoru.
        /// </summary>
        /// <param name="args"></param>
        void MouseChange(EditInteractiveEventArgs args);
        /// <summary>
        /// Vykreslí prvek v jeho aktuální pozici a stavu.
        /// Součástí vykreslení prvku může být určení oblastí, na nichž je prvek "aktivní" z hlediska myši, 
        /// a to naplněním dat do pole args.ActiveVisualAreaList. Pole obsahuje sadu souřadnic (RectangleF), 
        /// které se v editoru následně přenesou do interního registru a podle uvedených souřadnic 
        /// se následně bude aktivovat odpovídající prvek při další aktivitě myši.
        /// Pokud prvek nebude při Paint deklarovat svoje aktivní souřadnice, nebude 
        /// </summary>
        /// <param name="args"></param>
        void Paint(EditPaintEventArgs args);
    }
    #endregion
}
